﻿using System;
using System.Collections.Generic;
using System.Text;
using Tsanie.OsuDevice.Utils;
using System.IO;
using System.Net;
using Tsanie.OsuDevice.Components;
using System.Threading;

namespace Tsanie.OsuDevice.Network {
    class Downloader {
        public delegate void Func<T1, T2>(T1 param1, T2 param2);
        private DateTime dtBegin;
        private FileStream fileWriteStream;
        public TimeSpan DownloadedTimeSpan { get; private set; }

        public string BeatmapID { get; set; }
        public string OutFolder { get; set; }
        public Action<HttpWebRequest> RequestBefore { get; set; }
        public Func<string, int> ReadInfo { get; set; }
        public Func<int, TimeSpan> ReceiveFunc { get; set; }
        public Action<int> DoneFunc { get; set; }
        public Action<Exception> ExceptionFunc { get; set; }

        public string Filename { get; private set; }
        public string FilePath { get { return this.OutFolder + "\\" + this.Filename; } }
        public int FileLength { get; private set; }
        public int FileReceived { get; private set; }
        public bool CanPause { get; private set; }
        public DownloadState State { get; private set; }
        private string link;

        public Downloader() {
            dtBegin = DateTime.Now;
            DownloadedTimeSpan = TimeSpan.Zero;
        }

        public void ReleaseFile() {
            if (fileWriteStream != null) {
                fileWriteStream.Flush();
                fileWriteStream.Close();
                fileWriteStream.Dispose();
                fileWriteStream = null;
            }
        }
        public void PauseDownload() {
            this.State = DownloadState.Paused;
            ReleaseFile();
            DownloadedTimeSpan += DateTime.Now.Subtract(dtBegin);
        }
        public void StopDownload() {
            this.State = DownloadState.Paused;
            this.FileReceived = 0;
            ReleaseFile();
            DownloadedTimeSpan += DateTime.Now.Subtract(dtBegin);
        }
        public void StartDownload() {
            dtBegin = DateTime.Now;
            Download(0);
        }
        public void ResumeDownload() {
            this.State = DownloadState.Downloading;
            dtBegin = DateTime.Now;
            Download(this.FileReceived);
        }

        private void Download(int from) {
#if DEBUG
            Console.WriteLine("StartDownload - Request start.");
#endif
            //Download("http://localhost/nayuta%20-%20Shan%20Dara%20Rin%20[Insane].avi", "http://localhost/", null, from);
            //return;
            if (link == null) {
                HttpHelper.BeginConnect("http://osz.wo.tc/s/" + this.BeatmapID, null, (state) => {
                    bool flag = false;
                    if (Config.TryOszSite) {
                        using (StreamReader reader = new StreamReader(state.StreamResponse)) {
                            string line;
                            while ((line = reader.ReadLine()) != null) {
                                if (line.IndexOf("/images/nexist.png") > 0) {
                                    // 未找到
                                    break;
                                } else {
                                    int index = line.IndexOf("<a href=\"../d/");
                                    if (index > 0) {
                                        // 服务器上有
                                        line = line.Substring(index + 14);
                                        line = line.Substring(0, line.IndexOf("\" target=\"_blank\">"));
                                        this.CanPause = true;
                                        this.Filename = line;
                                        link = "http://osz.wo.tc/d/" + line;
                                        Download(link, "http://osz.wo.tc/s/" + this.BeatmapID,
                                            "_wau_r=1", from);
                                        flag = true;
                                        break;
                                    }
                                }
                            }
                            reader.Close();
                            reader.Dispose();
                        }
                        if (!flag) {
                            this.CanPause = false;
                            Download(Config.HttpHost + "/d/" + this.BeatmapID,
                                Config.HttpHost + "/s/" + this.BeatmapID,
                                Config.Cookies, from);
                        }
                    } else {
                        this.CanPause = false;
                        Download(Config.HttpHost + "/d/" + this.BeatmapID,
                            Config.HttpHost + "/s/" + this.BeatmapID,
                            Config.Cookies, from);
                    }
                }, (ex) => {
#if DEBUG
                    Console.WriteLine("高速下载站无法连接，即将尝试从主站下载。");
#endif
                    this.CanPause = false;
                    Download(Config.HttpHost + "/d/" + this.BeatmapID,
                        Config.HttpHost + "/s/" + this.BeatmapID,
                        Config.Cookies, from);
                });
            } else {
                Download(link, "http://osz.wo.tc/s/" + this.BeatmapID, "_wau_r=1", from);
            }
        }

        private void Download(string url, string refer, string cookie, int from) {
            this.State = DownloadState.Downloading;
            HttpHelper.BeginConnect(url, (request) => {
                request.Headers["Cookie"] = cookie;
                request.Referer = refer;
                if (from > 0)
                    request.AddRange(from);
                if (this.RequestBefore != null)
                    this.RequestBefore(request);
            }, (state) => {
                if (state.Response.ContentType != "text/html") {
                    // Success
                    bool flag = false;
                    if (this.FileLength == 0) {
                        this.FileLength = (int)state.Response.ContentLength;
                        flag = true;
                    }
                    if (this.Filename == null) {
                        string filename = state.Response.Headers["Content-Disposition"];
                        if (filename == null) {
                            filename = HttpUtility.UrlDecode(url.Substring(url.LastIndexOf('/') + 1));
                        } else {
                            string[] pars = filename.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
                            foreach (string par in pars) {
                                int index = par.IndexOf("filename=");
                                if (index >= 0) {
                                    filename = par.Substring(index + 9).Trim('\"');
                                }
                            }
                        }
                        this.Filename = filename;
                        flag = true;
                    }
                    if (flag && this.ReadInfo != null) {
                        this.ReadInfo(this.Filename, this.FileLength);
                    }
#if DEBUG
                    Console.WriteLine("StartDownload - Download start! => Filename: {0}, Length: {1}", filename, length);
#endif
                    FileInfo fi = new FileInfo(this.FilePath);
                    if ((from == 0 && fi.Exists) || (from > 0 && fi.Exists && fi.Length != from)) {
                        fi.Delete();
                    }
                    fileWriteStream = new FileStream(fi.FullName, FileMode.Append);
                    int n, count = this.FileReceived;
                    while (this.State == DownloadState.Downloading && ((n = state.StreamResponse.Read(state.Buffer, 0, state.Buffer.Length)) > 0)) {
                        if (fileWriteStream == null || !fileWriteStream.CanWrite)
                            return;
                        // 读取流块
                        if (count + n > this.FileLength) {
                            n = this.FileLength - count;    // 保持字节数正确
                        }
                        fileWriteStream.Write(state.Buffer, 0, n);
                        count += n;
                        this.FileReceived = count;
                        if (this.ReceiveFunc != null)
                            this.ReceiveFunc(count, this.DownloadedTimeSpan + DateTime.Now.Subtract(dtBegin));
                    }
                    if (this.State == DownloadState.Downloading) {
                        fileWriteStream.Flush();
                        fileWriteStream.Close();
                        fileWriteStream.Dispose();
                        this.State = DownloadState.Done;
                        if (this.DoneFunc != null)
                            this.DoneFunc(this.FileLength);
                    }
#if DEBUG
                        Console.WriteLine("StartDownload - Download done!");
#endif
                } else {
                    // Not download
                    string seconds = null;
                    using (StreamReader reader = new StreamReader(state.StreamResponse)) {
                        string line;
                        while (this.State == DownloadState.Downloading && (line = reader.ReadLine()) != null) {
                            int index = line.IndexOf("id='seconds'>");
                            if (index > 0) {
                                seconds = line.Substring(index + 13);
                                seconds = seconds.Substring(0, seconds.IndexOf('<'));
                            }
                        }
                    }
                    this.State = DownloadState.Error;
                    if (seconds != null) {
                        LogUtil.Error(new Exception("请等待 " + seconds + " 秒！"), this.ExceptionFunc);
                    } else {
                        LogUtil.Error(new Exception("下载失败，请检查是否登录。"), this.ExceptionFunc);
                    }
                }
            }, (ex) => {
                this.State = DownloadState.Error;
                DownloadedTimeSpan += DateTime.Now.Subtract(dtBegin);
                dtBegin = DateTime.Now;
                LogUtil.Error(ex, this.ExceptionFunc);
            });
        }
    }
}
